<?php
/** 
 * @brief 数据库Mysqli驱动类 
 */
class Db_Adapter_Mysqli extends Db_Adapter {
    /** 
     * Db_Adapter_Mysqli实例
     *
     * @var object 
     */
    private static $_instance = null;
	
    /**
     * 构造函数
     * 
     * @note 打开数据库连接
     * @param array $dsn 数据库DSN配置
     * 
     * @return void
     */
	private function __construct($dsn) {
        if (!extension_loaded('mysqli')) {
            throw new Exception('The mysqli extension must be loaded before use');
        }

        $this->_dsn = $dsn;
        $this->_conn = new mysqli($dsn['host'], $dsn['user'], $dsn['password'], $dsn['dbname'], $dsn['port']);
        if (mysqli_connect_errno()) {
            Log::save('[' . mysqli_connect_errno() . ']' . mysqli_connect_error(), Log::SQL);
            die(mysqli_connect_error());
        }
        
        if (isset($dsn['table_prefix'])) $this->tablePrefix = $dsn['table_prefix'];
	}
	
    /**
     * 取得数据库连接实例
     * 
     * @note 单件模式
     * @param array $dsn 数据库DSN配置
     * 
     * @return object
     */
    public static function connect($dsn) {
        if (self::$_instance === null) {
            self::$_instance = new self($dsn);
        }
        if (self::$_instance->_dsn != $dsn) {
            self::$_instance = new self($dsn);
        }
        if (!self::$_instance->_conn->ping()) {
            self::$_instance->_conn->close();
            self::$_instance = new self($dsn);
        }
        return self::$_instance;
    }

    /**
     * 返回从某个位置开始且指定行数的SQL查询语句
     * 
     * @param string $sql SQL语句
     * @param int $count 最大行数
     * @param int $offset 偏移量
     * 
     * @return string
     */
    public function limit($sql, $count = 10, $offset = 0) {
        $count = ($count > 0) ? $count : 10;
		$offset = ($offset > 0) ? $offset : 0;
		$sql .= sprintf(' LIMIT %s OFFSET %s', $count, $offset);
        return $sql;
    }
    
    /**
     * 查询并返回从某个位置开始且指定行数的记录
     * 
     * @param string $sql SQL语句
     * @param array $params 查询参数
     * @param int $count 最大行数
     * @param int $offset 偏移量
     * @param string $fetchMode 记录集的类型
     * 
     * @return mixed
     */
    public function selectLimit($sql, $params = array(), $count = 10, $offset = 0, $fetchMode = Db::FETCH_ASSOC) {
        $sql = $this->limit($sql, $count, $offset);
        return $this->getAll($sql, $params, $fetchMode);
    }

    /**
     * 关闭数据库连接
     * 
     * @return void
     */
    public function close() {
        $this->_conn->close();
    }
	
    /**
     * 执行INSERT/UPDATE/DELETE查询并返回受影响的记录数 
     * 
     * @param string $sql SQL语句
     * @param array $params 查询参数
     * 
     * @return int
     */ 
    public function execute($sql, $params = array()) {
        $sql = $this->parseSql($sql, $params);
        $this->_conn->query($sql) or $this->errorMsg($this->_conn->error);
        $this->_affectedRows = $this->_conn->affected_rows;
        return $this->_affectedRows;
    }

    /**
     * 执行SELECT查询并返回记录集对象 
     * 
     * @param string $sql SQL语句
     * @param array $params 查询参数
     * 
     * @return object
     */ 
    public function query($sql, $params = array()) {
        $sql = $this->parseSql($sql, $params);
        $stmt = $this->_conn->query($sql) or $this->errorMsg($this->_conn->error);
        $this->_numRows = mysqli_num_rows($stmt);
        return $stmt;
    }
    
    /**
     * 取得上一次执行INSERT/UPDATE/DELETE查询所影响的记录行数 
     * 
     * @return int
     */
    public function affectedRows() {
        return $this->_affectedRows;
    }

    /**
     * 取得上一次执行SELECT查询所返回的记录行数 
     * 
     * @return int
     */
    public function numRows() {
        return $this->_numRows;
    }
    
    /**
     * 取得最后一次执行INSERT操作所产生的主键值
     * 
     * @return mixed
     */ 
    public function insertId() {
        return $this->_conn->insert_id;
    }

    /**
     * 执行SELECT查询并返回某一页记录
     * 
     * @param string $sql SQL语句
     * @param array $params 查询参数
     * @param int $page 当前页码
     * @param int $pageSize 每页记录数
     * @param string $fetchMode 记录集的类型
     * 
     * @return mixed
     */ 
    public function getPage($sql, $params, $page, $pageSize = 10, $fetchMode = Db::FETCH_ASSOC) {
        $page = max(1, intval($page));
        $offset = ($page - 1) * $pageSize;
        return $this->selectLimit($sql, $params, $pageSize, $offset, $fetchMode);
    }
    
    /**
     * 执行SELECT查询并返回所有记录 
     * 
     * @param string $sql SQL语句
     * @param array $params 查询参数
     * @param string $fetchMode 记录集的类型
     * 
     * @return mixed
     */ 
    public function getAll($sql, $params = array(), $fetchMode = Db::FETCH_ASSOC) {
        $stmt = $this->query($sql, $params);
        $result = array();
        if ($this->_numRows > 0) {
            switch ($fetchMode) {
                case Db::FETCH_ASSOC:
                    $func = 'fetch_assoc';
                    break;
                case Db::FETCH_NUM:
                    $func = 'fetch_row';
                    break;
                case Db::FETCH_OBJ:
                    $func = 'fetch_object';
                    break;
                default:
                    $func = 'fetch_assoc';
            }
            while ($row = $stmt->$func()) {
                $result[] = $row;
            }
        }
        return $result;
    } 
    
    /**
     * 执行SELECT查询并返回第一条记录 
     * 
     * @param string $sql SQL语句
     * @param array $params 查询参数
     * @param string $fetchMode 记录集的类型
     * 
     * @return mixed
     */ 
    public function getRow($sql, $params = array(), $fetchMode = Db::FETCH_ASSOC) {
        $stmt = $this->query($sql, $params);
        $result = false;
        if ($this->_numRows > 0) {
            $stmt->data_seek(0);
            switch ($fetchMode) {
                case Db::FETCH_ASSOC:
                    $result = $stmt->fetch_assoc();
                    break;
                case Db::FETCH_NUM:
                    $result = $stmt->fetch_row();
                    break;
                case Db::FETCH_OBJ:
                    $result = $stmt->fetch_object();
                    break;
                default:
                    $result = $stmt->fetch_assoc();
            }
        }
        return $result;
    }
    
    /**
     * 执行SELECT查询并返回第一条记录的第一个字段值
     * 
     * @param string $sql SQL语句
     * @param array $params 查询参数
     * 
     * @return mixed
     */ 
    public function getOne($sql, $params = array()) {
        $stmt = $this->query($sql, $params);
        $result = false;
        if ($this->_numRows > 0) {
            $stmt->data_seek(0);
            $row = $stmt->fetch_row();
            $result = $row[0];
        }
        return $result;
    }

    /**
     * 启动事务处理 
     * 
     * @return boolean
     */ 
    public function startTrans() {
        return $this->_conn->autocommit(false);
    } 
    
    /**
     * 提交事务处理
     * 
     * @return boolean
     */ 
    public function commitTrans() {
        return $this->_conn->commit();
    } 
    
    /**
     * 回滚事务处理 
     * 
     * @return boolean
     */ 
    public function rollBack() {
        return $this->_conn->rollback();
    }
}
